/*
    This source file is part of GeNa project.
    Copyright © GeNa project 2008
    For the latest info, see http://code.google.com/p/genaproject/ or  http://lumpyproject.forums-rpg.com

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Foobar is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Foobar.  If not, see <http://www.gnu.org/licenses/>. */

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <time.h>

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#include <libgimp/gimp.h>


//Communication general data
typedef struct
{
    key_t key;
    int id;
    int size;
    char* offset;
} ShmStruct;

typedef struct
{
    key_t key;
    int id;
} MqStruct;

typedef struct
{
    ShmStruct shm_info;
    MqStruct  mq_info;
    MqStruct  bmq_info;

    int width, height; //Max width and height
} PluginData;

PluginData data;

//Image specific data
typedef struct
{
    int width, height;
} ImageData;

static void query(void);
static void run(    const gchar      *name,
                    gint             nparams,
                    const GimpParam *param,
                    gint             *nreturn_vals,
                    GimpParam        **return_vals);

GimpPlugInInfo PLUG_IN_INFO = {
    NULL,
    NULL,
    query,
    run
};

MAIN()

void init(void);
void quit(void) __attribute__((destructor));
void updateConnection();
void updateImage( ImageData* img, gint32 img_id);
void sendFileName(gint32 img_id);
void setSize(ImageData* img, gint32 img_id);

void init(void)
{
    printf("Blender Refresh Plugin : Start plugin\n");

    if( gimp_get_data("blender-refresh-plugin",&data)) //Check if the plugin have been already loaded, in this case there is nothing to do
        return;

    printf("Blender Refresh Plugin : Init sequence\n");

    data.width = 1; data.height = 1;
    data.shm_info.id = -1;
    data.shm_info.key = 0;
    data.shm_info.size = 0;
    data.shm_info.offset = NULL;

    data.mq_info.id = -1;
    data.mq_info.key = 0;

    data.bmq_info.id = -1;
    data.bmq_info.key = 0;

    //init random generator
    srand(time(0));

    //create Message Queue
    do
    {
        errno = 0;
        data.mq_info.key = getRandomKey();
        data.mq_info.id = msgget(data.mq_info.key, IPC_CREAT | IPC_EXCL);
    }
    while(errno == EEXIST);

    if( (errno != EEXIST) && errno)
    {
        printf("Blender Refresh Plugin : Failed to create a new message queue errno %d!\n", errno);
        return;
    }


    updateConnection();
}

void updateConnection()
{
    printf("Update connection\n");
    //Output key in file
    char key_out_filename[] = "/home/gregoire/.blender/bpydata/gimp_refresh/gimp_key";
    FILE* key_out = fopen( key_out_filename,"w+");

    if( !key_out)
    {
        printf("Blender Refresh Plugin : Couldn't export key to file %s !\n", key_out_filename);
        return;
    }

    fprintf(key_out, "%d", data.mq_info.key);
    fclose( key_out);

    //Find if Blender script is started

    char key_in_filename[] = "/home/gregoire/.blender/bpydata/gimp_refresh/blender_key";
    FILE* key_in = fopen( key_in_filename,"w+");

    if( !key_in)
    {
        printf("Blender Refresh Plugin : Couldn't import key from file %s !\n", key_out_filename);
        return;
    }

    fscanf(key_in, "%d", (int*)data.bmq_info.key);
    if( key_in)

    data.bmq_info.id = msgget( data.mq_info.key,0);

    //Save data
    gimp_set_data("blender-refresh-plugin", &data, sizeof(data));
}

void quit(void)
{
    printf("Blender Refresh Plugin : Close plugin.\n");

return;
    if( data.mq_info.id)
    {
        msgctl( data.mq_info.id, IPC_RMID, NULL);
    }

    //Output null key in file
    char key_out_filename[] = "/home/gregoire/.blender/bpydata/gimp_refresh/gimp_key";
    FILE* key_out = fopen( key_out_filename,"w+");

    if( !key_out)
    {
        printf("Blender Refresh Plugin : Couldn't export null key to file %s !", key_out_filename);
        return;
    }

    fprintf(key_out, "0");
    fclose( key_out);
}

static void query(void)
{
    static GimpParamDef args[] = {
    {
        GIMP_PDB_INT32,
        "run-mode",
        "Run mode",
    },
    {
        GIMP_PDB_IMAGE,
        "image",
        "Input image"
    },
    {
        GIMP_PDB_DRAWABLE,
        "drawable",
        "Input Drawable"
    }
    };

    gimp_install_procedure(
        "plug-in-blender-refresh",
        "Refresh Blender",
        "It refresh Blender texture after modification in The Gimp.",
        "Egru & Samuncles",
        NULL,
        NULL,
        "Refresh Blender",
        "*",
        GIMP_PLUGIN,
        G_N_ELEMENTS(args), 0,
        args, NULL);

    gimp_install_procedure(
        "plug-in-blender-refresh-connection",
        "Refresh Blender Plugin Connection",
        "It refresh Blender texture after modification in The Gimp.",
        "Egru & Samuncles",
        NULL,
        NULL,
        "Refresh Blender Connection",
        "*",
        GIMP_PLUGIN,
        G_N_ELEMENTS(args), 0,
        args, NULL);

    gimp_plugin_menu_register("plug-in-blender-refresh","<Image>/Image");
    gimp_plugin_menu_register("plug-in-blender-refresh-connection","<Image>/Image");
}

static void run(    const gchar      *name,
                    gint             nparams,
                    const GimpParam *param,
                    gint             *nreturn_vals,
                    GimpParam        **return_vals)
{
    g_message("Run");

    init();


    static GimpParam values[1];
    GimpPDBStatusType status = GIMP_PDB_SUCCESS;
    GimpRunMode run_mode;

    *nreturn_vals = 1;
    *return_vals = values;

    values[0].type = GIMP_PDB_STATUS;
    values[0].data.d_status = status;

    run_mode = param[0].data.d_int32;


    if( strcmp(name, "plug-in-blender-refresh-connection") == 0)
    {
        updateConnection();
        return;
    }
    else
    {
        gint img = param[1].data.d_int32;

        if( !gimp_image_get_filename(img))
            return;

        GimpParasite* parasite = gimp_image_parasite_find(img,"blender_data_image");

        if( !parasite)
        {
            ImageData img_data;
            img_data.width = 0;
            img_data.height = 0;

            setSize(&img_data, img);
            gimp_image_attach_new_parasite(img,"blender_data_image",0,sizeof(ImageData),&img_data);
            return;
        }

        setSize((ImageData*)parasite->data, img);
    }
}

key_t getRandomKey(void)
{
    int key;

    do
    {
        key = ((int)((double)rand() / ((double)RAND_MAX + 1) * 0xffffffff)) + 1;
    } while( key == IPC_PRIVATE);

    return (key_t)key;
}

void loadSharedMemory()
{
    if( data.width*data.height*4 <= data.shm_info.size)
        return;

    //Load a new shared memory segment
    int size;
    size = data.width*data.height*4;
    size = (size & ~4096) + ((size & 4096) ? 4096 : 0); //4096 is the page size on most computer

    do
    {
        errno = 0;
        data.shm_info.key = getRandomKey();
        data.shm_info.id = shmget(data.shm_info.key, IPC_CREAT | IPC_EXCL);
    }
    while(errno == EEXIST);

    if( (errno != EEXIST) && errno)
    {
        printf("Blender Refresh Plugin : Failed to create a new shared memory segment errno %d!\n", errno);
        return;
    }

    data.shm_info.size = size;
    data.shm_info.offset = (char*)shmat(data.shm_info.id,NULL,0);

    if(data.bmq_info.id != -1)
    {
        char msg[] = "\0\0\0\1change_sm";

        //Send message to the blender plugin
        msgsnd( data.bmq_info.id, (void*)msg, 10,0);
        msgsnd( data.bmq_info.id, (void*)&data.shm_info.key,0,0); //Send key
    }
}

void setSize(ImageData* img, gint32 img_id)
{
    if( (img->width != gimp_image_width(img_id)) ||
        (img->height != gimp_image_height(img_id)))
    {
        img->width = gimp_image_width(img_id);
        img->height = gimp_image_height(img_id);
    }

    if( ( img->width > data.width) || ( img->height > data.height))
    {
        if( img->width > data.width)
            data.width = img->width;
        if( img->height > data.height)
            data.height = img->width;

        loadSharedMemory();
    }

    updateImage( img, img_id);

    if(data.bmq_info.id != -1)
    {
        char msg[] = "\0\0\0\1update";

        //Send message to the blender plugin
        msgsnd( data.bmq_info.id, (void*)msg, 7,0);
        sendFileName(img_id);
        msgsnd( data.bmq_info.id, (void*)&img->width,0,0); //Send width
        msgsnd( data.bmq_info.id, (void*)&img->height,0,0); //Send height
    }
}

void sendFileName(gint32 img_id)
{
    gchar* filename = gimp_image_get_filename(img_id);
    if( !filename)
    {
        char msg[] = "\0\0\0\1\0";
        msgsnd( data.bmq_info.id, &msg, 1,0); //Empty filename
        return;
    }

    int offset = 0;
    int end = 0;
    while(filename[end])
    {
        if( filename[end] == '\\')
            offset = end;
        end++;
    }
    end++; //Copy the null character

    char* msg = malloc(end - offset + 4);
    *(int*)msg = 1;

    int i = offset;
    for( ; i < end; i++)
        msg[i - offset + 4] = filename[i];

    msgsnd( data.bmq_info.id, (void*)msg, end-offset,0);

    free(msg);
}

void updateImage( ImageData* img, gint32 img_id)
{
    gint32 layer = gimp_layer_new_from_visible(img_id,img_id,"bgp_layer");
    gint bpp = gimp_drawable_bpp(layer);

    char* dest = data.shm_info.offset;

    int i=0;
    int j=0;
    for( ; i< img->height; i++)
    {
        for( j=0; j < img->width; j++)
        {
            guint8* pixel = gimp_drawable_get_pixel(layer,j,i,&bpp);

            if( bpp >= 1)
                *dest = pixel[0];
            else
                *dest = 0;
            dest++;

            if( bpp >= 2)
                *dest = pixel[1];
            else
                *dest = 0;
            dest++;

            if( bpp >= 3)
                *dest = pixel[2];
            else
                *dest = 0;
            dest++;

            if( bpp >= 4)
                *dest = pixel[3];
            else
                *dest = 0;
            dest++;
        }
    }

}
